﻿/*
 Copyright (c) 2003-2012, CKSource - Frederico Knabben. All rights reserved.
 For licensing, see LICENSE.html or http://ckeditor.com/license
 */

(function () {
    CKEDITOR.htmlParser.filter = CKEDITOR.tools.createClass(
        {
            $: function (rules) {
                this._ =
                {
                    elementNames: [],
                    attributeNames: [],
                    elements: {$length: 0},
                    attributes: {$length: 0}
                };

                if (rules)
                    this.addRules(rules, 10);
            },

            proto: {
                addRules: function (rules, priority) {
                    if (typeof priority != 'number')
                        priority = 10;

                    // Add the elementNames.
                    addItemsToList(this._.elementNames, rules.elementNames, priority);

                    // Add the attributeNames.
                    addItemsToList(this._.attributeNames, rules.attributeNames, priority);

                    // Add the elements.
                    addNamedItems(this._.elements, rules.elements, priority);

                    // Add the attributes.
                    addNamedItems(this._.attributes, rules.attributes, priority);

                    // Add the text.
                    this._.text = transformNamedItem(this._.text, rules.text, priority) || this._.text;

                    // Add the comment.
                    this._.comment = transformNamedItem(this._.comment, rules.comment, priority) || this._.comment;

                    // Add root fragment.
                    this._.root = transformNamedItem(this._.root, rules.root, priority) || this._.root;
                },

                onElementName: function (name) {
                    return filterName(name, this._.elementNames);
                },

                onAttributeName: function (name) {
                    return filterName(name, this._.attributeNames);
                },

                onText: function (text) {
                    var textFilter = this._.text;
                    return textFilter ? textFilter.filter(text) : text;
                },

                onComment: function (commentText, comment) {
                    var textFilter = this._.comment;
                    return textFilter ? textFilter.filter(commentText, comment) : commentText;
                },

                onFragment: function (element) {
                    var rootFilter = this._.root;
                    return rootFilter ? rootFilter.filter(element) : element;
                },

                onElement: function (element) {
                    // We must apply filters set to the specific element name as
                    // well as those set to the generic $ name. So, add both to an
                    // array and process them in a small loop.
                    var filters = [this._.elements['^'], this._.elements[element.name], this._.elements.$],
                        filter, ret;

                    for (var i = 0; i < 3; i++) {
                        filter = filters[i];
                        if (filter) {
                            ret = filter.filter(element, this);

                            if (ret === false)
                                return null;

                            if (ret && ret != element)
                                return this.onNode(ret);

                            // The non-root element has been dismissed by one of the filters.
                            if (element.parent && !element.name)
                                break;
                        }
                    }

                    return element;
                },

                onNode: function (node) {
                    var type = node.type;

                    return type == CKEDITOR.NODE_ELEMENT ? this.onElement(node) :
                        type == CKEDITOR.NODE_TEXT ? new CKEDITOR.htmlParser.text(this.onText(node.value)) :
                            type == CKEDITOR.NODE_COMMENT ? new CKEDITOR.htmlParser.comment(this.onComment(node.value)) :
                                null;
                },

                onAttribute: function (element, name, value) {
                    var filter = this._.attributes[name];

                    if (filter) {
                        var ret = filter.filter(value, element, this);

                        if (ret === false)
                            return false;

                        if (typeof ret != 'undefined')
                            return ret;
                    }

                    return value;
                }
            }
        });

    function filterName(name, filters) {
        for (var i = 0; name && i < filters.length; i++) {
            var filter = filters[i];
            name = name.replace(filter[0], filter[1]);
        }
        return name;
    }

    function addItemsToList(list, items, priority) {
        if (typeof items == 'function')
            items = [items];

        var i, j,
            listLength = list.length,
            itemsLength = items && items.length;

        if (itemsLength) {
            // Find the index to insert the items at.
            for (i = 0; i < listLength && list[i].pri < priority; i++) { /*jsl:pass*/
            }

            // Add all new items to the list at the specific index.
            for (j = itemsLength - 1; j >= 0; j--) {
                var item = items[j];
                if (item) {
                    item.pri = priority;
                    list.splice(i, 0, item);
                }
            }
        }
    }

    function addNamedItems(hashTable, items, priority) {
        if (items) {
            for (var name in items) {
                var current = hashTable[name];

                hashTable[name] =
                    transformNamedItem(
                        current,
                        items[name],
                        priority);

                if (!current)
                    hashTable.$length++;
            }
        }
    }

    function transformNamedItem(current, item, priority) {
        if (item) {
            item.pri = priority;

            if (current) {
                // If the current item is not an Array, transform it.
                if (!current.splice) {
                    if (current.pri > priority)
                        current = [item, current];
                    else
                        current = [current, item];

                    current.filter = callItems;
                }
                else
                    addItemsToList(current, item, priority);

                return current;
            }
            else {
                item.filter = item;
                return item;
            }
        }
    }

    // Invoke filters sequentially on the array, break the iteration
    // when it doesn't make sense to continue anymore.
    function callItems(currentEntry) {
        var isNode = currentEntry.type
            || currentEntry instanceof CKEDITOR.htmlParser.fragment;

        for (var i = 0; i < this.length; i++) {
            // Backup the node info before filtering.
            if (isNode) {
                var orgType = currentEntry.type,
                    orgName = currentEntry.name;
            }

            var item = this[i],
                ret = item.apply(window, arguments);

            if (ret === false)
                return ret;

            // We're filtering node (element/fragment).
            if (isNode) {
                // No further filtering if it's not anymore
                // fitable for the subsequent filters.
                if (ret && ( ret.name != orgName
                    || ret.type != orgType )) {
                    return ret;
                }
            }
            // Filtering value (nodeName/textValue/attrValue).
            else {
                // No further filtering if it's not
                // any more values.
                if (typeof ret != 'string')
                    return ret;
            }

            ret != undefined && ( currentEntry = ret );
        }

        return currentEntry;
    }
})();

// "entities" plugin
/*
 {
 text : function( text )
 {
 // TODO : Process entities.
 return text.toUpperCase();
 }
 };
 */
